#include "bundle.h"
#include <string.h>
#include "common.h"
#include "addresses.h"
#include "conversion.h"
#include "kerl.h"
#include "transaction.h"
//#include "diag/Trace.h"

//extern Conversion* pConversion;
extern Keccak384* pKeccak384;

#define FAST_TRIT_INCREMENT

namespace Bundle {

	namespace {
#ifdef FAST_TRIT_INCREMENT
		// representing the value of the 82nd trit, i.e. 3^81
		const uint32_t TRIT_82[12] = {0xd56d7cc3, 0xb6bf0c69, 0xa149e834,
											 0x4d98d5ce, 0x1};
#endif

		inline int decrementTryte(int max, tryte_t *tryte)
			{
				const int slack = *tryte - MIN_TRYTE_VALUE;
				if (slack <= 0) {
					return 0;
				}

				const int dec = MIN(max, slack);
				*tryte -= dec;

				return dec;
			}

		inline int incrementTryte(int max, tryte_t *tryte)
		{
			const int slack = MAX_TRYTE_VALUE - *tryte;
			if (slack <= 0) {
				return 0;
			}

			const int inc = MIN(max, slack);
			*tryte += inc;

			return inc;
		}

		void normalizeHashFragment(tryte_t *fragment_trytes)
		{
			int sum = 0;
			for (unsigned int j = 0; j < 27; j++) {
				sum += fragment_trytes[j];
			}

			for (unsigned int j = 0; j < 27; j++) {
				if (sum > 0) {
					sum -= decrementTryte(sum, &fragment_trytes[j]);
				}
				else if (sum < 0) {
					sum += incrementTryte(-sum, &fragment_trytes[j]);
				}
				if (sum == 0) {
					break;
				}
			}
		}

		inline void normalizeHash(tryte_t *hash_trytes)
		{
			for (unsigned int i = 0; i < 3; i++) {
				normalizeHashFragment(hash_trytes + i * 27);
			}
		}

		void normalizeHashBytes(const unsigned char *hash_bytes,
								 tryte_t *normalized_hash_trytes)
		{
			Conversion conv;
			conv.bytes_to_trytes(hash_bytes, normalized_hash_trytes);
			normalizeHash(normalized_hash_trytes);
		}

#if 0
		/** @return Whether all values sum up to zero. */
		bool validateBalance(Transaction* tx)
		{
			int64_t value = 0;
			if (!tx) {
				return true;
			}
			do {
				value += tx->getValue();
				tx = tx->getNext();
			} while (tx);
			return value == 0;
		}

		/** @return Whether every input transaction has meta transactions. */
		bool validateMetaTX(const BundleCTX *ctx, uint8_t security)
		{
			for (uint8_t i = 0; i <= ctx->last_tx_index; i++) {
				// only input transactions have meta transactions
				if (isInputTX(ctx, i)) {
					const unsigned char *input_addr_bytes =
						getAddressBytes(ctx, i);

					for (uint8_t j = 1; j < security; j++) {
						if (i + j > ctx->last_tx_index || ctx->values[i + j] != 0) {
							return false;
						}
						if (memcmp(input_addr_bytes,
									  getAddressBytes(ctx, i + j),
									  NUM_HASH_BYTES) != 0) {
							return false;
						}
					}
				}
			}
			return true;
		}

		/**
		 * @return Whether the index of the change address is higher than all input
		 * indices.
		 */
		bool validateChangeIndex(const BundleCTX *ctx,
										  uint8_t change_tx_index)
		{
			// if there is no change transaction, this is always valid
			if (change_tx_index > ctx->last_tx_index) {
				return true;
			}

			for (uint8_t i = 0; i <= ctx->last_tx_index; i++) {
				if (ctx->values[i] < 0 &&
					ctx->indices[i] >= ctx->indices[change_tx_index]) {
					return false;
				}
			}

			return true;
		}

		/** @return Whether the provided seed indices match the addresses. */
		bool validateAddressIndices(const BundleCTX *ctx,
											 uint8_t change_tx_index,
											 const unsigned char *seed_bytes,
											 uint8_t security)
		{
			for (uint8_t i = 0; i <= ctx->last_tx_index; i++) {
				// only check the change and input addresses
				if (i == change_tx_index || isInputTX(ctx, i)) {
					const unsigned char *addr_bytes = getAddressBytes(ctx, i);

					if (!validateAddress(addr_bytes, seed_bytes, ctx->indices[i],
										  security)) {
						return false;
					}
				}
			}

			return true;
		}

		/** @return Whether each address occures only once in the bundle. */
		bool validateAddressReuse(const BundleCTX *ctx)
		{
			for (uint8_t i = 0; i <= ctx->last_tx_index; i++) {

				if (ctx->values[i] == 0) {
					continue;
				}
				const unsigned char *addr_bytes = getAddressBytes(ctx, i);

				for (uint8_t j = i + 1; j <= ctx->last_tx_index; j++) {
					if (ctx->values[j] != 0 &&
						memcmp(addr_bytes, getAddressBytes(ctx, j),
								  NUM_HASH_BYTES) == 0) {
						return false;
					}
				}
			}

			return true;
		}

		int validateBundle(const BundleCTX *ctx, uint8_t change_tx_index,
								   const unsigned char *seed_bytes, uint8_t security)
		{
			if (!validateBalance(ctx)) {
				return NONZERO_BALANCE;
			}
			if (!validateMetaTX(ctx, security)) {
				return INVALID_META_TX;
			}
			if (!validateChangeIndex(ctx, change_tx_index)) {
				return CHANGE_IDX_LOW;
			}
			if (!validateAddressIndices(ctx, change_tx_index, seed_bytes, security)) {
				return INVALID_ADDRESS_INDEX;
			}
			if (!validateAddressReuse(ctx)) {
				return ADDRESS_REUSED;
			}

			return OK;
		}
#endif

		bool validateHash(const BundleCTX *ctx)
		{
			tryte_t hash_trytes[81];
			normalizeHashBytes(ctx->hash, hash_trytes);

			if (memchr(hash_trytes, MAX_TRYTE_VALUE, 81) != NULL) {
				return false;
			}

			return true;
		}

		void computeHash(BundleCTX* ctx, Transaction* tx)
		{
			if (!tx) {
				return;
			}
			Kerl::initialize(pKeccak384);
			do {
				Kerl::absorbBytes(pKeccak384, tx->getBundleBytes(), 96);
				tx = tx->getNext();
			} while (tx);
			Kerl::squeezeFinalChunk(pKeccak384, ctx->hash);
		}

	}

#if 0
	int validatingFinalize(BundleCTX *ctx, uint8_t change_tx_index,
						   const unsigned char *seed_bytes,
						   uint8_t security)
	{
		if (hasOpenTXs(ctx)) {
			THROW(INVALID_STATE);
		}

		int result = validateBundle(ctx, change_tx_index, seed_bytes, security);
		if (result != OK) {
			return result;
		}

		computeHash(ctx);
		if (!validateHash(ctx)) {
			// if the hash is invalid, reset it to zero
			os_memset(ctx->hash, 0, 48);
			return UNSECURE_HASH;
		}
		return OK;
	}
#endif

	const unsigned char* getHash(const BundleCTX *ctx)
	{
		return ctx->hash;
	}

	void getNormalizedHash(const BundleCTX *ctx, tryte_t *hash_trytes)
	{
		Conversion conv;
		conv.bytes_to_trytes(getHash(ctx), hash_trytes);
		normalizeHash(hash_trytes);
	}

	void bytesIncrementTritArea81(uint8_t* bytes)
	{
		Conversion conv;
	#ifdef FAST_TRIT_INCREMENT
		uint32_t bigint[12];
		conv.bytes_to_bigint(bytes, bigint);
		conv.bigint_add(bigint, bigint, TRIT_82);
		conv.bigint_to_bytes(bigint, bytes);
	#else
		trit_t trits[NUM_HASH_TRITS];
		conv.bytes_to_trits(bytes, trits);
		conv.increment_trit_aera(trits, 81, 81);
		conv.trits_to_bytes(trits, bytes);
	#endif
	}


	/** @brief Finalizes the bundle by computing the valid bundle hash.
	 *  @param ctx the bundle context used.
	 *  @return tag increment of the first transaction that was necessary to
	 *          generate a valid bundle
	 */
	unsigned int finalize(BundleCTX *ctx, Transaction* tx, bool fixMBug)
	{
		unsigned int tag_increment = 0;
#if 0
		if (hasOpenTXs(ctx)) {
			THROW(INVALID_STATE);
		}
#endif
		computeHash(ctx, tx);

		if (!fixMBug)
			return tag_increment;
#ifdef FAST_TRIT_INCREMENT
		tryte_t trytes[NUM_HASH_TRYTES];
		Conversion conv;
		conv.bytes_to_trytes(tx->getBundleBytes() + 48, trytes);
		trytes[27+26] = 0;	// overflow protection for faster increment
		conv.trytes_to_bytes(trytes, tx->getBundleBytes() + 48);
#endif
		while (!validateHash(ctx)) {
			// increment the tag of the first transaction
			bytesIncrementTritArea81(tx->getBundleBytes() + 48);
			computeHash(ctx, tx);
			tag_increment++;
		}

		// the not normalized hash is already in the result pointer
		return tag_increment;
	}
}
